#import "Escaper.h"
#import "Term.h"
#import "Modem.h"
#import "Source.h"
#import "StringResponder.h"
#import "Fdset.h"
#import <stdio.h>
#import <ctype.h>
#import <libc.h>

#define MAXARGS 10
#define stripNL(temp) if (temp[strlen(temp)-1] == '\n') temp[strlen(temp)-1] = 0;

struct scriptCommand {
  char *cmd;
  enum escapeFunction func;
};

static struct scriptCommand decode[] =
{
  {"nop",nop},
  {"stop",stop},
  {"protocol", protocol},
  {"parity",parity},
  {"send",sendfile},
  {"receive",receivefile},
  {"capture",capture},
  {"hangup",hangup},
  {"baudrate",baudrate},
  {"echo",echo},
  {"say",say},
  {"expect",expect},
  {"dial",dial},
  {"readscript",readscript}
};

#define NUL '\0'
void breakIntoWords(char *line, char *words[])
{
  char *p = line;
  int i;

  for(i=0; *p; i++) {
    while( *p && isspace(*p))
      p++;
    if (*p == NUL) break;
    words[i]=p;
    while( *p && !isspace(*p))
      p++;
    if (*p)
      *p++ = NUL;
  }
  words[i]=NULL;
}

int member(int ch, char * set)
{
  return(strchr(set,ch) != 0);
}

@implementation Escaper

+ newEscaperWithTerm:(id)t andModem:(id)m
{
  int i;
  self = [super new];
  term = t;
  modem = m;
  for (i=0; i<128; i++) keymap[i]=nop;
  keymap['b']=baudrate;
  keymap['d']=dial;
  keymap[CTRL('d')]=stop;
  keymap['p']=protocol;
  keymap[CTRL('p')]=parity;
  keymap['s']=sendfile;
  keymap['r']=receivefile;
  keymap['c']=capture;
  keymap['h']=hangup;
  keymap['e']=readscript;   // for (e)xecute script file
  return self;
}

- readScript:(char *)file
{
  FILE *fin;
  char line[200], *args[MAXARGS];
  int i;
  id source;

  fin = fopen(file,"r");
  if (fin==NULL) {
    sprintf(line,"couldn't open script file %s\n",file);
    [term putString:line];
    return nil;
  }
  while (fgets(line,sizeof(line),fin)) {
    breakIntoWords(line,args);
    if (args[0]==NULL || *args[0]=='%') continue;  // % is for comments
    for(i=0; i<sizeof(decode)/sizeof(decode[0]); i++) {
      if (strcmp(args[0],decode[i].cmd) == 0) {    // then we found a command
	source = [Source newStrings:args+1];
	[self doFunction:decode[i].func withInput:source];
      }
    }
  }
  return self;
}  
  

- doFunction:(enum escapeFunction) func withInput:(id)source
{
  char temp[200];
  switch (func) {
  case dial: {
    char dialPrefix[]="ATDT", dialPostfix[]="\r"; // constants for now; later local var's of modem
    char pn[200];

    [source getline:pn size:sizeof(pn) WithPrompt:"Enter phone number:"];
    sprintf(temp,"%s%s%s\n",dialPrefix, pn, dialPostfix);
    [self autoDial:temp];
    break;
  }
  case baudrate: {
    int bps;
    [source getline:temp size:sizeof(temp) WithPrompt:"Enter new baud rate [300,1200,2400,4800,9600]:"];
    sscanf(temp,"%d",&bps);
    [modem setSpeed:bps];
    sprintf(temp,"Modem speed set to %d\n",[modem getSpeed]);
    [term putString:temp];
    break;
  }
  case readscript:
    [source getline:temp size:sizeof(temp) WithPrompt:"Enter script file:"];
    stripNL(temp);
    [self readScript:temp];
    break;
  case stop:
    [term reset];
    [modem endCapture];
    [term putString:"\nquitting \n"];
    exit(0);
    break;
  case protocol: {
    enum direction dir;
    char p;
    do {
      p = [source getcharWithPrompt:"\nEnter direction [(s)end,(r)eceive]:"];
    } while (!member(p,"sr"));
    dir = (p=='s')?tohost:fromhost;
    sprintf(temp, "\nEnter desired %s protocol [(x)modem,xmodem(1)k,(y)modem,(z)modem]:",
	    (dir==tohost)?"send":"receive");
    do {
      p = [source getcharWithPrompt:temp];
    } while (!member(p,"x1yz"));
    [modem setProtocol:p dir:dir];
    sprintf(temp,"\nSend protocol is %c\n",[modem getProtocolDir:tohost]);
    [term putString:temp];
    sprintf(temp,"\nReceive protocol is %c\n",[modem getProtocolDir:fromhost]);
    [term putString:temp];
    break;
  }
  case parity: {
    int par, parity;
    do {
      par = [source getcharWithPrompt:"\nEnter desired parity (even, odd, or none) [e,o,n]:"];
      if (par=='e' || par=='E')
	parity=EVENP;
      else if (par=='o' || par=='O')
	parity=ODDP;
      else if (par=='n' || par=='N')
	parity=NOPARITY;
      else
	parity=ILLEGAL_PARITY;
    } while (parity == ILLEGAL_PARITY);
    [modem setParity:parity];
    sprintf(temp,"\nParity is %c\n",par);
    [term putString:temp];
    break;
  }
  case sendfile:
    {
      char trigger[100];
      struct stat dummy;
      int cc;

      do {
	[source getline:temp size:sizeof(temp) WithPrompt:"\nEnter file to send:"];
	stripNL(temp);
	// check for existence of file
	if ((cc = stat(temp, &dummy)) != 0)
	  [source putString:"Couldn't open file...try again"];
      } while (temp[0] && cc != 0);  // null filename means abort command
      if (temp[0]) {
	[source getline:trigger size:sizeof(trigger) WithPrompt:"\nEnter command to send to host:"];
	[[term pushState] reset];
	[modem writeOut:trigger];
	[modem sendFile:temp];
	[term popState];
      }
      break;
    }
  case receivefile:
    [[term pushState] reset];
    [modem receive];
    [term popState];
    break;
  case capture:
    if ([modem isCapturing]) {
      [term putString:"Ending capture to file\n"];
      [modem endCapture];
    }
    else {
      [source getline:temp size:sizeof(temp) WithPrompt:"\nEnter file to save to [tip.record]:"];
      stripNL(temp);
      if (*temp == 0)
	strcpy(temp,"tip.record");
      [modem captureSessionInFile:temp];
    }
    break;
  case hangup:
    [[term pushState] reset];
    [modem hangup];
    [term popState];
    break;
  case nop:
    break;
  case echo:
    while ([source getline:temp size:sizeof(temp)]) {
      [term putString:temp];
      [term putString:" "];
    }
    [term putString:"\n"];
    break;
  case say:
    {
      int firstarg=1;
      while ([source getline:temp size:sizeof(temp)]) {
	if (firstarg)
	  firstarg = 0;
	else
	  [modem writeOut:" "];
	[modem writeOut:temp];
      }
      [modem writeOut:"\r"];
    }
    break;
    case expect:
    {
	// can't write this bit just yet
    }
    break;
  default:
    break;
  }
  return self;
}

- doEscape:(char) func
{
  return [self doFunction:keymap[func] withInput:term];
}

- bindChar:(char) ch toFunction:(enum escapeFunction) func
{
  keymap[ch] = func;
  return self;
}

- autoDial:(char *)dialString
{
  id s;
  id sr;
  int i;
  char line[200];
  enum responses {connect=1,connect1200,connect2400,no_carrier,busy,no_dialtone,user_abort};
  enum responses seen;

  s = [Fdset new];
  [s addfd:[modem getfd]];
  [s addfd:[term getfd]];
  sr = [StringResponder new];
  [sr addString:"CONNECT!" returnValue:connect];
  [sr addString:"CONNECT 1200!" returnValue:connect1200];
  [sr addString:"CONNECT 2400!" returnValue:connect2400];
  [sr addString:"NO CARRIER!" returnValue:no_carrier];
  [sr addString:"BUSY!" returnValue:busy];
  [sr addString:"NO DIALTONE!" returnValue:no_dialtone];
  try_again:
  [modem writeOut:dialString paced:10000];
  for (seen=0; !seen && [s waitOnInputFor:60]; ) {
    if ([s getReadyfd] == [term getfd]) { // check for user abort
      [term readInto:line];
      if (strchr(line,27)) {
	seen = user_abort;
	break;
      }
    }
    [modem readInto:line];
    [term putString:line];
    for (i=0;i<strlen(line);i++) {  // convert end of line chars to !
      if (line[i]=='\r' || line[i]=='\n') line[i]='!';
    }
    seen = [sr inputString:line];
  }
  if (!seen)
    [term putString:"modem is not responding as expected\n"];
  else {
    switch (seen) {
    case connect:
      [term ringBell];
      break;
    case connect1200:
      [modem setSpeed:1200];
      [term ringBell];
      break;
    case connect2400:
      [modem setSpeed:2400];
      [term ringBell];
      break;
    case no_carrier:
      [term putString:"modem detected no carrier, trying again...\n"];
      goto try_again;
      break;
    case busy:
      [term putString:"busy, trying again...\n"];
      sleep(10);
      goto try_again;
      break;
    case no_dialtone:
      [term putString:"modem couldn't get dialtone; check phone\n"];
      break;
    case user_abort:
      [term putString:"User aborted dialing\n"];
      break;
    }
  }
  [s free];
  [sr free];
  return self;
}
  

@end
